home *** CD-ROM | disk | FTP | other *** search
/ Language/OS - Multiplatform Resource Library / LANGUAGE OS.iso / smaltalk / manchest.lha / MANCHESTER / usenet / st80_pre4 / NewsReader.st < prev    next >
Text File  |  1993-07-24  |  29KB  |  1,036 lines

  1. "    NAME        NewsReader
  2.     AUTHOR        CWatts@BNR.CA (Carl Watts)
  3.     FUNCTION    A USENET news reader
  4.     ST-VERSION    2.5
  5.     PREREQUISITES    
  6.     CONFLICTS    
  7.     DISTRIBUTION    world
  8.     VERSION        1
  9.     DATE         16 May 1991
  10. SUMMARY
  11. A news-reading tool.  Needs direct access to the news spool
  12. directories. 
  13. "!
  14. "
  15. From: CWatts@BNR.CA (Carl Watts)
  16. Newsgroups: comp.lang.smalltalk
  17. Subject: A NewsReader implemented in Smalltalk-80
  18. Message-ID: <1991May16.170855.10950@bqnes74.bnr.ca>
  19. Date: Thu, 16 May 91 18:08:55 BST
  20.  
  21. At last!!  I promised myself I wouldn't post messages here until I had
  22. an nice tool in Smalltalk to read and send messages.  Well it took a
  23. couple of weeks but I've got it now...  A nice browser-like interface
  24. to it to.  I hated 'rn'.  So here it is, for the rest of you.
  25.  
  26. Its implemented in a class called NewsReader.  To get one just do
  27. 'NewsReader open'.  It needs to have access to the directories where
  28. the messages are stored.  It needs this to be able to read the
  29. messages.  The default place it looks for these is '/usr/spool/news'
  30. but you can set where to look with a class method.  To post messages
  31. it uses Unix's 'inews' behind the scenes.  The class comment will tell
  32. you a bit more about how to use it.
  33.  
  34. My NewsReader remembers what news groups you've subscribed to and what
  35. messages in them you've read.  Its quite intelligent about when to
  36. stop showing you messages that you've already read past.  Thats
  37. important so you don't always see big lists of messages you've already
  38. read.
  39.  
  40. The following fileIn contains two small additions to Smalltalk
  41. classes.  Both are backwardly compatible so it won't hurt anything
  42. else.  The modification to SelectionInListView is useful for many
  43. other applications.  The modification to TextView is to fix a bug.
  44.  
  45. Use it as you would like, if you make any improvements/additions to
  46. it, please send me them and maybe I'll include it in my next version
  47. if I like them.  Currently it works in Smalltalk 2.5.  It a very
  48. simple matter to change it for Smalltalk 4.0.  I'll get around to that
  49. sometime.
  50.  
  51.  
  52. From: CWatts@BNR.CA (Carl Watts)
  53. Newsgroups: comp.lang.smalltalk
  54. Subject: Long lines in Usenet postings
  55. Message-ID: <1991May21.151653.22354@bqnes74.bnr.ca>
  56. Date: Tue, 21 May 91 16:16:53 BST
  57.  
  58. Several people now have sent me comments about the long lines in my
  59. postings (like the NewsReader in Smalltalk) that appear mangled with
  60. split words and inserted lines.
  61.  
  62. These problems are caused by bugs in the Usenet message transfers
  63. agents (like inews) that transfer messages between NewsServers.
  64. Different ones impose different abitrary limits on the length of lines
  65. they transfer and this causes them to split words/lines/paragraphs by
  66. inserting additional carriage returns in the most unfortunate places.
  67.  
  68. When these bugs are fixed, the message transfer agents will handle
  69. standard ASCII files properly, without the need to insert arbitrary
  70. word/character wrapping during transfer.  It should be entirely the
  71. responsiblity of the tool showing you news messages to properly
  72. word-wrap them for display given the size of the window/screen it has
  73. available to show them to you.
  74.  
  75. Those of us raised on word processors (not typewriters) only type
  76. Returns at the end of paragraphs.  And my messages will be somewhat
  77. mangled by the news transfer agents until they get those bugs fixed.
  78.  
  79. For those using my NewsReader (or even other newsreaders) who want to
  80. be more accomodating to these bugs in the usenet software but who
  81. don't want to have to remember to type Return every 72 characters, you
  82. can pipe your posted message through 'fmt' before it goes to 'inews'.
  83. This will wordwrap your file to some LCLL (lowest common linelength)
  84. like 72 characters.  Just modify NewsReader postMessageText:from: to
  85. say:
  86.  
  87.     UnixProcess cshOne: 'fmt -s ""', fileName asString, '"" | inews'
  88.  
  89. Of course this is almost just as bad as what the news servers do, but
  90. fmt will word-wrap not character-wrap and will only do it once.  The
  91. news servers can character-wrap several times at abitrary points
  92. leaving you're text completely mangled.  Even this way you have to be
  93. careful when running source code through fmt since it can insert a
  94. carriage return in a String literal.  Its much better to fix the bugs
  95. rather than bastardizing other software to accomodate the bugs.
  96. "
  97.  
  98.  
  99. 'From Objectworks for Smalltalk-80(tm), Version 2.5 of 29 July 1989 on 16 May 1991 at 12:48:49 pm'!
  100.  
  101. 'Added classes:
  102.     NewsReader
  103.  
  104. Method changes:
  105.     TextView
  106.         Changed methods:
  107.             displayView
  108.  
  109.     SelectionInListView
  110.         Changed methods:
  111.             list:
  112.  
  113. '!
  114. Model subclass: #NewsReader
  115.     instanceVariableNames: 'chapter section message subsection area subscribedGroup '
  116.     classVariableNames: 'MessageTemplate NewsDirectory Subscribed '
  117.     poolDictionaries: ''
  118.     category: 'Tools-Mail'!
  119. NewsReader comment:
  120. 'Instances of this class allow reading and browsing and posting messages on UseNet in Smalltalk.  By Carl Watts (cwatts@BNR.ca)
  121.  
  122. The user interface is very Smalltalk browser like.  It allows you to
  123. read messages in any order you like just by clicking on the message
  124. subject.  You can name newsgroups that you wish to subscribe to.  And
  125. you can easily post new messages to these newsgroups.
  126.  
  127. Unlike other newsreaders, this one needs to have the message
  128. directories of the newsserver mounted on this machines filesystem in
  129. order to read/view the messages.  This newsreader will look for the
  130. message directories under ''/mnt/spool/news'' by default but you can
  131. set it to be whereever you like by sending a message to the class.  If
  132. I ever find out what the protocol is to communicate through a Unix
  133. Socket to a newsserver, I can easily modify this newsreader to user
  134. that medium.  But for now, it ne
  135.  
  136. access the files directly.
  137.  
  138. For posting messages, this newsreader uses the standard "inews"
  139. command at the OS level (if one can use the term "OS" so broadly as to
  140. include Unix).  "inews" must be available in the current path.
  141.  
  142. This newsreader also keeps a standard inews message header that you
  143. can customize with your personal information.
  144.  
  145. When you open a new instance of the receiver, there are three views,
  146. two ListViews at the top and a TextView of the bottom.  The first
  147. ListView lists the newsgroups you currently subscribe to.  You can
  148. subscribe to new ones by selecting ''subscribe...'' from the menu of
  149. this view.  The second ListView shows the messages (by Subject) in the
  150. currently selected newsgroup (selected in the first ListView).  The
  151. TextView shows you the text of the message if a message is selected.
  152. Or it shows a preformated
  153.  
  154. r to post a message to the currently selected newsgroup, if one is
  155. selected.  Or if no newsgroup is selected then it shows you the
  156. default header for messages.  You can change this default header
  157. however you like and ''accept'' it.
  158.  
  159. To post a new message to a newsgroup, select the newsgroup (and don''t
  160. select any messages in the newsgroup).  The TextView will show a
  161. preformatted message header for a new message.  Add the text that you
  162. wish after the header (leaving at least one blank line between the
  163. header and the start of your message).  When you are satsified with
  164. your message, select ''post'' from the menu and your new message will
  165. be posted to that newsgroup.
  166.  
  167. This class was created by Carl Watts (cwatts@BNR.ca).  If you like
  168. this class and use it, send me an email message about it. '!
  169.  
  170.  
  171. !NewsReader methodsFor: 'chapter list'!
  172.  
  173. chapter
  174.  
  175. "Answer the current chapter."
  176.  
  177.     ^chapter!
  178.  
  179. chapter: aChapter
  180.  
  181. "Set the current chapter."
  182.  
  183.     chapter _ aChapter.
  184.     self newSectionList: section!
  185.  
  186. chapterDirectory
  187.  
  188. "Answer the directory where the chapters appears."
  189.  
  190.     | dir |
  191.     area isNil ifTrue: [^nil].
  192.     dir _ NewsDirectory construct: area.
  193.     dir isReadable
  194.         ifTrue: [^dir]
  195.         ifFalse: [^nil]!
  196.  
  197. chapterList
  198.  
  199. "Answer the SequencableCollection of chapters."
  200.  
  201.     | dir contents |
  202.  
  203.     dir _ self chapterDirectory.
  204.     dir isNil ifTrue: [^nil].
  205.     contents _ dir directoryContents
  206.         select: [:each | self isGroupName: each].
  207.     ^contents asSortedCollection!
  208.  
  209. chapterMenu
  210.  
  211. "Answer the menu for the chapter view."
  212.  
  213.     chapter isNil ifTrue: [^nil].
  214.     section isNil ifFalse: [^nil].
  215.     ^ActionMenu
  216.         labelList: #(('subscribe' 'unsubscribe'))
  217.         selectors: #(subscribeGroup unsubscribeGroup)!
  218.  
  219. newChapterList: initialSelection
  220.  
  221. "Show a new chapter list with initialSelection as the first selection."
  222.  
  223.     chapter _ initialSelection.
  224.     self changed: #chapter! !
  225.  
  226. !NewsReader methodsFor: 'section list'!
  227.  
  228. newSectionList: initialSelection
  229.  
  230. "Show a new section list with initialSelection as the first selection."
  231.  
  232.     section _ initialSelection.
  233.     self changed: #section!
  234.  
  235. section
  236.  
  237. "Answer the current section."
  238.  
  239.     ^section!
  240.  
  241. section: aSection
  242.  
  243. "Set the current section."
  244.  
  245.     section _ aSection.
  246.     self newSubsectionList: subsection!
  247.  
  248. sectionDirectory
  249.  
  250. "Answer the directory where the sections appear."
  251.  
  252.     chapter isNil ifTrue: [^nil].
  253.     ^(self chapterDirectory) construct: chapter!
  254.  
  255. sectionList
  256.  
  257. "Answer the SequencableCollection of sections."
  258.  
  259.     | dir contents |
  260.  
  261.     dir _ self sectionDirectory.
  262.     dir isNil ifTrue: [^nil].
  263.     contents _ dir directoryContents
  264.         select: [:each | self isGroupName: each].
  265.     ^contents asSortedCollection!
  266.  
  267. sectionMenu
  268.  
  269. "Answer the menu for the section view."
  270.  
  271.     section isNil ifTrue: [^nil].
  272.     subsection isNil ifFalse: [^nil].
  273.     ^ActionMenu
  274.         labelList: #(('subscribe' 'unsubscribe'))
  275.         selectors: #(subscribeGroup unsubscribeGroup)! !
  276.  
  277. !NewsReader methodsFor: 'subsection list'!
  278.  
  279. newSubsectionList: initialSelection
  280.  
  281. "Show a new subsection list with initialSelection as the first selection."
  282.  
  283.     subsection _ initialSelection.
  284.     self changed: #subsection!
  285.  
  286. subsection
  287.  
  288. "Answer the current subsection."
  289.  
  290.     ^subsection!
  291.  
  292. subsection: aSubsection
  293.  
  294. "Set the current subsection."
  295.  
  296.     subsection _ aSubsection!
  297.  
  298. subsectionDirectory
  299.  
  300. "Answer the directory where the subsections appear."
  301.  
  302.     section isNil ifTrue: [^nil].
  303.     ^(self sectionDirectory) construct: section!
  304.  
  305. subsectionList
  306.  
  307. "Answer the SequencableCollection of subsections."
  308.  
  309.     | dir contents |
  310.  
  311.     dir _ self subsectionDirectory.
  312.     dir isNil ifTrue: [^nil].
  313.     contents _ dir directoryContents
  314.         select: [:each | self isGroupName: each].
  315.     ^contents asSortedCollection!
  316.  
  317. subsectionMenu
  318.  
  319. "Answer the menu for the subsection view."
  320.  
  321.     subsection isNil ifTrue: [^nil].
  322.     ^ActionMenu
  323.         labelList: #(('subscribe' 'unsubscribe'))
  324.         selectors: #(subscribeGroup unsubscribeGroup)! !
  325.  
  326. !NewsReader methodsFor: 'message list'!
  327.  
  328. message
  329.  
  330. "Answer the current message."
  331.  
  332.     ^message!
  333.  
  334. message: aMessage
  335.  
  336. "Set the current message."
  337.  
  338.     message _ aMessage.
  339.     message notNil ifTrue: [self readMessage].
  340.     self newText!
  341.  
  342. messageItem: aMessage
  343.  
  344. "Answer the SequencableCollection of messages."
  345.  
  346.     | file stream title |
  347.  
  348.     file _ self currentDirectory construct: aMessage printString.
  349.     stream _ file readStream.
  350.     [stream skipToAll: 'Subject: '; skipSeparators.
  351.      title _ stream upTo: Character cr] valueNowOrOnUnwindDo: [stream close].
  352.  
  353.     title isEmpty
  354.         ifTrue: [^'no subject']
  355.         ifFalse: [^title copyFrom: 10 to: title size]!
  356.  
  357. messageList
  358.  
  359. "Answer the SequencableCollection of messages."
  360.  
  361.     | dir contents cutOff eachNumber |
  362.  
  363.     dir _ self currentDirectory.
  364.     dir isNil ifTrue: [^nil].
  365.     cutOff _ self cutOffMessageNumber.
  366.     contents _ SortedCollection sortBlock: [:a :b | a >= b].
  367.     dir directoryContents do: [:each |
  368.         (self isMessageName: each) ifTrue: [
  369.             eachNumber _ each asNumber.
  370.             eachNumber > cutOff ifTrue: [contents add: eachNumber]]].
  371.     ^contents!
  372.  
  373. messageMenu
  374.  
  375. "Answer the menu for the message view."
  376.  
  377.     message isNil
  378.         ifTrue: [^nil]
  379.         ifFalse: [^ActionMenu
  380.             labelList: #("('reply...')" ('save as...') ('hardcopy'))
  381.             selectors: #("replyMessage" saveMessage hardcopyMessage)]!
  382.  
  383. newMessageList
  384.  
  385. "Show a new message list."
  386.  
  387.     self changed: #message!
  388.  
  389. newMessageList: initialSelection
  390.  
  391. "Show a new message list with initialSelection as the first selection."
  392.  
  393.     message _ initialSelection.
  394.     self newMessageList! !
  395.  
  396. !NewsReader methodsFor: 'text'!
  397.  
  398. acceptText: aText from: aController
  399.  
  400. "Accept changed text from aController."
  401.  
  402.     self amViewingBlankMessage ifTrue: [^self postMessageText: aText from: aController].
  403.     ^self acceptMessageTemplateText: aText from: aController!
  404.  
  405. newText
  406.  
  407. "Show a new text."
  408.  
  409.     self changed: #text!
  410.  
  411. text
  412.  
  413. "Answer the text to be displayed in the text view."
  414.  
  415.     self amViewingMessage ifTrue: [^self textForMessage].
  416.     self amViewingBlankMessage ifTrue: [^self textForBlankMessage].
  417.     ^self textForMessageTemplate!
  418.  
  419. textForMessage
  420.  
  421. "Answer the text for the message currently being viewed."
  422.  
  423.     ^(self currentDirectory construct: message printString) contentsOfEntireFile asText!
  424.  
  425. textMenu
  426.  
  427. "Answer the menu for the text view."
  428.  
  429.     self amViewingMessage ifTrue: [^self textMenuForMessage].
  430.     self amViewingBlankMessage ifTrue: [^self textMenuForBlankMessage].
  431.     ^self textMenuForMessageTemplate!
  432.  
  433. textMenuForMessage
  434.  
  435. "Answer the menu for the text view if a message is currently being viewed."
  436.  
  437.     ^ActionMenu
  438.         labelList: #(('copy'))
  439.         selectors: #(copySelection)! !
  440.  
  441. !NewsReader methodsFor: 'area list'!
  442.  
  443. area
  444.  
  445. "Answer the current area."
  446.  
  447.     ^area!
  448.  
  449. area: anArea
  450.  
  451. "Set the current area."
  452.  
  453.     area _ anArea.
  454.     self newChapterList: chapter!
  455.  
  456. areaList
  457.  
  458. "Answer the SequencableCollection of areas."
  459.  
  460.     | dir contents |
  461.     dir _ NewsDirectory.
  462.     contents _ dir directoryContents
  463.         select: [:each | self isGroupName: each].
  464.     ^contents asSortedCollection!
  465.  
  466. areaMenu
  467.  
  468. "Answer the menu for the area view."
  469.  
  470.     area isNil ifTrue: [^nil].
  471.     chapter isNil ifFalse: [^nil].
  472.     ^ActionMenu
  473.         labelList: #(('subscribe'))
  474.         selectors: #(subscribeGroup)!
  475.  
  476. newAreaList: initialSelection
  477.  
  478. "Show a new area list with initialSelection as the first selection."
  479.  
  480.     area _ initialSelection.
  481.     self changed: #area! !
  482.  
  483. !NewsReader methodsFor: 'current group/directory'!
  484.  
  485. currentDirectory
  486.  
  487. "Answer the directory in which I can currently see messages."
  488.  
  489.     | dir groupName part |
  490.  
  491.     groupName _ self subscribedGroup.
  492.     groupName isNil ifTrue: [^nil].
  493.     dir _ NewsDirectory.
  494.     [part _ groupName copyUpTo: $..
  495.      dir _ dir construct: part.
  496.      part = groupName ifTrue: [groupName _ ''] ifFalse: [
  497.         groupName _ groupName copyFrom: part size + 2 to: groupName size].
  498.      groupName isEmpty] whileFalse.
  499.     dir isReadable
  500.         ifTrue: [^dir]
  501.         ifFalse: [^nil]!
  502.  
  503. currentGroup
  504.  
  505. "Answer the current group (newsgroup)."
  506.  
  507.     | group |
  508.  
  509.     group _ ''.
  510.     area notNil ifTrue: [group _ area].
  511.     chapter notNil ifTrue: [group _ group, '.', chapter].
  512.     section notNil ifTrue: [group _ group, '.', section].
  513.     subsection notNil ifTrue: [group _ group, '.', subsection].
  514.     ^group! !
  515.  
  516. !NewsReader methodsFor: 'testing'!
  517.  
  518. amViewingBlankMessage
  519.  
  520. "Answer if the user is currently viewing a blank message."
  521.  
  522.     ^self message isNil & self subscribedGroup notNil!
  523.  
  524. amViewingMessage
  525.  
  526. "Answer if the user is currently view a message."
  527.  
  528.     ^self message notNil!
  529.  
  530. amViewingMessageTemplate
  531.  
  532. "Answer if the user is currently viewing the blank message template."
  533.  
  534.     ^self message isNil & self subscribedGroup isNil!
  535.  
  536. isGroupName: aString
  537.  
  538. "Answer if this is part of the name of a group (not a message name)."
  539.  
  540.     ^(self isMessageName: aString) not and: [aString first ~= $.]!
  541.  
  542. isMessageName: aString
  543.  
  544. "Answer if aString is the name of a message file."
  545.  
  546.     ^aString first isDigit & aString last isDigit! !
  547.  
  548. !NewsReader methodsFor: 'subscription'!
  549.  
  550. amSubscribedGroup: aGroup
  551.  
  552. "Answer if the group aGroup is subscribed to."
  553.  
  554.     ^Subscribed includesKey: aGroup!
  555.  
  556. subscribeGroup
  557.  
  558. "Begin subscribing to the current group."
  559.  
  560.     self subscribeGroup: self currentGroup!
  561.  
  562. subscribeGroup: aGroup
  563.  
  564. "Begin subscribing to aGroup."
  565.  
  566.     | readEntry |
  567.     (self amSubscribedGroup: aGroup) ifFalse: [
  568.         readEntry _ Array new: 4.
  569.         readEntry
  570.             at: 1 put: Date today;
  571.             at: 2 put: 0;
  572.             at: 3 put: 0;
  573.             at: 4 put: 0.
  574.         Subscribed at: aGroup put: readEntry.
  575.         self subscribedGroup isNil ifTrue: [self newSubscribedGroupList: nil]]!
  576.  
  577. unsubscribeGroup
  578.  
  579. "Unsubscribe to the current group."
  580.  
  581.     self unsubscribeGroup: self subscribedGroup!
  582.  
  583. unsubscribeGroup: aGroup
  584.  
  585. "Unsubscribe to the group aGroup."
  586.  
  587.     (self amSubscribedGroup: aGroup) ifTrue: [
  588.         Subscribed removeKey: aGroup.
  589.         self newSubscribedGroupList: nil]! !
  590.  
  591. !NewsReader methodsFor: 'new messages'!
  592.  
  593. checkReadEntry
  594.  
  595. "Check the entry in the Subscribed Dictionary for the current subscribedGroup and update it if necessary."
  596.  
  597.     | readEntry |
  598.     readEntry _ Subscribed at: self subscribedGroup.
  599.     (readEntry at: 1) ~= Date today ifTrue: [
  600.         readEntry at: 4 put: (readEntry at: 3).
  601.         (readEntry at: 4) isNil ifTrue: [readEntry at: 4 put: 0].
  602.         readEntry at: 3 put: (readEntry at: 2).
  603.         readEntry at: 1 put: Date today]!
  604.  
  605. cutOffMessageNumber
  606.  
  607. "Answer the message number of the newest message that is older than you want to see."
  608.  
  609.     self checkReadEntry.
  610.     ^(Subscribed at: self subscribedGroup) at: 4!
  611.  
  612. readMessage
  613.  
  614. "Remember that I read the currently selected message."
  615. "Update the read entry for that group."
  616.  
  617.     | readEntry |
  618.     self checkReadEntry.
  619.     readEntry _ Subscribed at: self subscribedGroup.
  620.     readEntry at: 2 put: ((readEntry at: 2) max: self message).!
  621.  
  622. readMessage: aMessageNumber group: aGroup
  623.  
  624. "I have read the specified message in aGroup."
  625. "Update the read entry for that group."
  626.  
  627.     | readEntry |
  628.     readEntry _ Subscribed at: aGroup.
  629.     (readEntry at: 1) ~= Date today ifTrue: [
  630.         readEntry at: 4 put: (readEntry at: 3).
  631.         readEntry at: 3 put: (readEntry at: 2).
  632.         readEntry at: 1 put: Date today].
  633.     readEntry at: 2 put: ((readEntry at: 2) max: aMessageNumber).!
  634.  
  635. updateNewMessageGroupsIn: aDirectory group: aGroup
  636.  
  637. "Update the NewMessageGroups in aDirectory."
  638.  
  639.     | oldHigh contents thisGroup |
  640.  
  641.     contents _ aDirectory directoryContents.
  642.     oldHigh _ self highestReadIn: aGroup.
  643.     (contents detect: [:each | (self isMessageName: each) and: [each asNumber > oldHigh]] ifNone: [nil]) notNil
  644.         ifTrue: [self newMessagesIn: aGroup].
  645.     contents do: [:each | 
  646.         (self isGroupName: each) ifTrue: [
  647.             thisGroup _ (aGroup isEmpty) ifTrue: [each] ifFalse: [aGroup, '.', each].
  648.             (self amSubscribedGroup: thisGroup) ifTrue: [
  649.                 self updateNewMessageGroupsIn: (aDirectory construct: each) group: thisGroup]]]! !
  650.  
  651. !NewsReader methodsFor: 'message list functions'!
  652.  
  653. hardcopyMessage
  654.  
  655. "Print a copy of the message to the printer."
  656.  
  657.     | document |
  658.     document _ Document new.
  659.     document startParagraph.
  660.     document addText: self text.
  661.     document close.
  662.     document toPrinter!
  663.  
  664. messageTitle
  665.  
  666. "Answer the title string for the currently selected message."
  667.  
  668.     ^(self messageItem: self message) asString!
  669.  
  670. saveMessage
  671.  
  672. "Save the currently selected message to a file."
  673.  
  674.     | aString filename stream |
  675.     aString _ FillInTheBlank request: 'Enter file name'
  676.         initialAnswer: (self subscribedGroup asFilename construct: self messageTitle) asString.
  677.     aString isEmpty ifTrue: [^self].
  678.     filename _ aString asFilename.
  679.     (filename exists not or: [self confirm: 'Filename already exists.  Use anyway?']) ifTrue: [
  680.         filename directory exists ifFalse: [filename directory makeDirectory].
  681.         stream _ filename writeStream.
  682.         [stream nextPutAll: self text asString] valueNowOrOnUnwindDo: [stream close]]! !
  683.  
  684. !NewsReader methodsFor: 'message template'!
  685.  
  686. acceptMessageTemplateText: aText from: aController
  687.  
  688. "Accept changed text for the message template from aController."
  689.  
  690.     aText isEmpty ifTrue: [^true].
  691.     MessageTemplate _ aText asString.
  692.     ^true!
  693.  
  694. messageTemplate
  695.  
  696. "Answer the string that is the message template."
  697.  
  698.     ^MessageTemplate!
  699.  
  700. textForMessageTemplate
  701.  
  702. "Answer the text for the message template."
  703.  
  704.     ^self messageTemplate asText allBold!
  705.  
  706. textMenuForMessageTemplate
  707.  
  708. "Answer the menu for the text view if the message is currently being viewed/edited."
  709.  
  710.     ^ActionMenu
  711.         labelList: #(('again' 'undo') ('copy' 'cut' 'paste') ('accept' 'cancel'))
  712.         selectors: #(again undo copySelection cut paste accept cancel)! !
  713.  
  714. !NewsReader methodsFor: 'blank message'!
  715.  
  716. postMessageText: aText from: aController
  717.  
  718. "Post the message in aText.  aText came from aController."
  719.  
  720.     | fileName fileStream resultString |
  721.     aText isEmpty ifTrue: [^true].
  722.     fileName _ 'temp news' asFilename.
  723.     fileStream _ fileName writeStream.
  724.     [fileStream nextPutAll: aText asString] valueNowOrOnUnwindDo: [fileStream close].
  725.     resultString _ UnixProcess cshOne: 'inews "', fileName asString, '"'.
  726.     resultString isEmpty
  727.         ifTrue: [
  728.             fileName delete.
  729.             self newMessageList: message.
  730.             ^true]
  731.         ifFalse: [
  732.             [self error: resultString] fork.
  733.             ^false]!
  734.  
  735. textForBlankMessage
  736.  
  737. "Answer the text for a blank message for the current group."
  738.  
  739.     | cr |
  740.  
  741.     cr _ String with: Character cr.
  742.     ^('Newsgroups: ', self subscribedGroup, cr,
  743.         'Subject: ', cr,
  744.         self messageTemplate, cr, cr) asText!
  745.  
  746. textMenuForBlankMessage
  747.  
  748. "Answer the menu for the text view if a blank message is currently being viewed/edited."
  749.  
  750.     ^ActionMenu
  751.         labelList: #(('again' 'undo') ('copy' 'cut' 'paste') ('post' 'cancel'))
  752.         selectors: #(again undo copySelection cut paste accept cancel)! !
  753.  
  754. !NewsReader methodsFor: 'subscribed group list'!
  755.  
  756. newSubscribedGroupList
  757.  
  758. "Update the subscribedGroupList."
  759.  
  760.     self changed: #subscribedGroup!
  761.  
  762. newSubscribedGroupList: initialSelection
  763.  
  764. "Update the subscribedGroupList and make initialSelection the initial selection."
  765.  
  766.     subscribedGroup _ initialSelection.
  767.     self newSubscribedGroupList!
  768.  
  769. subscribedGroup
  770.  
  771. "Answer the currently selected subscribed group."
  772.  
  773.     ^subscribedGroup!
  774.  
  775. subscribedGroup: aGroup
  776.  
  777. "Set the currently selected subscribed group."
  778.  
  779.     subscribedGroup _ aGroup.
  780.     self newMessageList!
  781.  
  782. subscribedGroupList
  783.  
  784. "Answer the list of subscribed groups."
  785.  
  786.     ^Subscribed keys asSortedCollection!
  787.  
  788. subscribedGroupMenu
  789.  
  790. "Answer the menu for the subscribed group list."
  791.  
  792.     self subscribedGroup isNil ifTrue: [^ActionMenu
  793.         labelList: #(('update') ('subscriptions...') ('save setup to file' 'retrieve setup from file'))
  794.         selectors: #(updateSubscribedList openGroupBrowser saveSetup retrieveSetup)].
  795.     ^ActionMenu
  796.         labelList: #(('unread all' 'unsubscribe'))
  797.         selectors: #(unreadAll unsubscribeGroup)! !
  798.  
  799. !NewsReader methodsFor: 'subscribed group functions'!
  800.  
  801. openGroupBrowser
  802.  
  803. "Open a browser on all the groups accessable by the receiver."
  804.  
  805.     self class openAllGroupViewOn: self!
  806.  
  807. retrieveSetup
  808.  
  809. "Retrieve the current settings for NewsReader from a file."
  810.  
  811.     self class retrieveSetup.
  812.     self newSubscribedGroupList!
  813.  
  814. saveSetup
  815.  
  816. "Save the current settings of the receiver to a file so they can be later retrieved."
  817.  
  818.     self class saveSetup!
  819.  
  820. unreadAll
  821.  
  822. "Unread all the messages that have been read in this group.  This will cause all available messages in this group to appear in the message list."
  823.  
  824.     (Subscribed at: self subscribedGroup) at: 4 put: 0.
  825.     self newMessageList!
  826.  
  827. updateSubscribedList
  828.  
  829. "Update the subscribed list."
  830.  
  831.     self newSubscribedGroupList! !
  832. "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
  833.  
  834. NewsReader class
  835.     instanceVariableNames: ''!
  836.  
  837.  
  838. !NewsReader class methodsFor: 'setup'!
  839.  
  840. defaultSetupFile
  841.  
  842. "Answer the filename of the default setup file."
  843.  
  844.     ^Filename named: 'NewsReaderSetup'!
  845.  
  846. newsDirectory
  847.  
  848. "Answer the filename of the directory where the news files are maintained."
  849.  
  850.     ^NewsDirectory!
  851.  
  852. newsDirectory: aFilename
  853.  
  854. "Set the filename of the directory where the news files are maintained."
  855. "NewsReader newsDirectory: '/usr/spool/news'"
  856.  
  857.     NewsDirectory _ aFilename asFilename!
  858.  
  859. retrieveSetup
  860.  
  861. "Retrieve the current settings (subscriptions, etc.) from a file called 'NewsReaderSetup'."
  862.  
  863.     | retrieveFilename |
  864.     self defaultSetupFile isReadable
  865.         ifTrue: [^self retrieveSetupFrom: self defaultSetupFile].
  866.  
  867.     retrieveFilename _ FillInTheBlank request: 'Name of NewsReader Setup file?'.
  868.     retrieveFilename isEmpty ifFalse: [self retrieveSetupFrom: retrieveFilename asFilename].!
  869.  
  870. retrieveSetupFrom: aFilename
  871.  
  872. "Retrieve the current settings (subscriptions, etc.) from a file."
  873.  
  874.     | stream |
  875.     stream _ aFilename asFilename readStream.
  876.     [(Number readFrom: stream) = 1 ifFalse: [self error: 'Unknown Format'].
  877.      self newsDirectory: (String readFrom: stream).
  878.      MessageTemplate _ String readFrom: stream.
  879.      Subscribed _ Dictionary readFrom: stream] valueNowOrOnUnwindDo: [stream close]!
  880.  
  881. saveSetup
  882.  
  883. "Save the current settings (subscriptions, etc.) to a file which can be retrieved (via retrieveSetup) later."
  884.  
  885.     self saveSetupAs: self defaultSetupFile!
  886.  
  887. saveSetupAs: aFilename
  888.  
  889. "Save the current settings (subscriptions, etc.) to a file which can be retrieved (via retrieveSetup) later."
  890.  
  891.     | stream |
  892.     stream _ aFilename asFilename writeStream.
  893.     [1 storeOn: stream. stream space.
  894.      NewsDirectory asString storeOn: stream. stream space.
  895.      MessageTemplate storeOn: stream. stream space.
  896.      Subscribed storeOn: stream] valueNowOrOnUnwindDo: [stream close]! !
  897.  
  898. !NewsReader class methodsFor: 'scheduling'!
  899.  
  900. open
  901.  
  902. "Open a new instance of the receiver."
  903.  
  904.     self openSubscribedGroupViewOn: self new!
  905.  
  906. openAllGroupViewOn: aNewsReader
  907.  
  908. "Open a view on aNewsReader showing all accessable newsgroups."
  909.  
  910.     | topView chapterView sectionView subsectionView areaView |
  911.  
  912.     topView _ StandardSystemView new.
  913.     topView model: aNewsReader;
  914.         label: self name, ' ', NewsDirectory asString;
  915.         "icon: CollapsedIcon;"
  916.         "iconText: (self iconTextFor: aFileDirectoryList fileDirectory);"
  917.         minimumSize: 400@100;
  918.         borderWidth: 1.
  919.  
  920.     areaView _ SelectionInListView on: aNewsReader
  921.         aspect: #area change: #area:
  922.         list: #areaList menu: #areaMenu initialSelection: #area.
  923.     topView addSubView: areaView in: (0@0 corner: 0.25@1) borderWidth: 1.
  924.  
  925.     chapterView _ SelectionInListView on: aNewsReader
  926.         aspect: #chapter change: #chapter:
  927.         list: #chapterList menu: #chapterMenu initialSelection: #chapter.
  928.     topView addSubView: chapterView in: (0.25@0 corner: 0.5@1) borderWidth: 1.
  929.  
  930.     sectionView _ SelectionInListView on: aNewsReader
  931.         aspect: #section change: #section:
  932.         list: #sectionList menu: #sectionMenu initialSelection: #section.
  933.     topView addSubView: sectionView in: (0.5@0 corner: 0.75@1) borderWidth: 1.
  934.  
  935.     subsectionView _ SelectionInListView on: aNewsReader
  936.         aspect: #subsection change: #subsection:
  937.         list: #subsectionList menu: #subsectionMenu initialSelection: #subsection.
  938.     topView addSubView: subsectionView in: (0.75@0 corner: 1@1) borderWidth: 1.
  939.  
  940.     topView controller open!
  941.  
  942. openSubscribedGroupViewOn: aNewsReader
  943.  
  944. "Open a view on the subscribed groups accessable by aNewsReader."
  945.  
  946.     | topView messageView textView subscribedGroupView |
  947.  
  948.     topView _ StandardSystemView new.
  949.     topView model: aNewsReader;
  950.         label: self name, ' ', NewsDirectory asString;
  951.         "icon: CollapsedIcon;"
  952.         "iconText: (self iconTextFor: aFileDirectoryList fileDirectory);"
  953.         minimumSize: 400@200;
  954.         borderWidth: 1.
  955.  
  956.     subscribedGroupView _ SelectionInListView on: aNewsReader
  957.         aspect: #subscribedGroup change: #subscribedGroup:
  958.         list: #subscribedGroupList menu: #subscribedGroupMenu initialSelection: #subscribedGroup.
  959.     topView addSubView: subscribedGroupView in: (0@0 corner: 0.3@0.25) borderWidth: 1.
  960.  
  961.     messageView _ SelectionInListView on: aNewsReader
  962.         printItems: #messageItem: oneItem: false
  963.         aspect: #message change: #message:
  964.         list: #messageList menu: #messageMenu initialSelection: #message.
  965.     topView addSubView: messageView in: (0.3@0 corner: 1@0.25) borderWidth: 1.
  966.  
  967.     textView _ TextView on: aNewsReader
  968.         aspect: #text change: #acceptText:from:
  969.         menu: #textMenu.
  970.     topView addSubView: textView in: (0@0.25 corner: 1@1) borderWidth: 1.
  971.  
  972.     topView controller open! !
  973.  
  974. !NewsReader class methodsFor: 'initialization'!
  975.  
  976. initialize
  977.  
  978. "Intialize the class."
  979.  
  980.     self newsDirectory isNil ifTrue: [self newsDirectory: '/usr/spool/news'].
  981.     Subscribed isNil ifTrue: [Subscribed _ Dictionary new].
  982.     MessageTemplate isNil ifTrue: [self initializeMessageTemplate]!
  983.  
  984. initializeMessageTemplate
  985.  
  986. "Intialize the default message template for new messages."
  987.  
  988.     MessageTemplate _
  989. 'From: userid@hostname.DOMAIN (full name)
  990. Organization: your organization name'! !
  991.  
  992. NewsReader initialize!
  993.  
  994.  
  995.  
  996. !SelectionInListView methodsFor: 'list access'!
  997.  
  998. list: anArray
  999.     "Set the receiver's list to be anArray."
  1000.     "Modified by Carl Watts so the the printItems parameter can be a message to send to the model to convert each item in anArray into the appropriate text for the list item. "
  1001.  
  1002.     | item theList|
  1003.     itemList _ anArray.
  1004.     anArray == nil ifTrue:
  1005.         [isEmpty _ true.
  1006.         selection _ 0.
  1007.         ^self changeModelSelection: 0].
  1008.     isEmpty _ false.
  1009.     printItems isSymbol
  1010.         ifTrue: [theList _ anArray collect: [:each | (model perform: printItems with: each)]]
  1011.         ifFalse: [printItems
  1012.             ifTrue: [theList _ anArray collect: [:each | each printString copyUpTo: Character cr]]
  1013.             ifFalse: [theList _ anArray]].
  1014.     list _ TextList onList:
  1015.         (topDelimiter == nil
  1016.             ifTrue: [theList]
  1017.             ifFalse: [(Array with: topDelimiter) ,
  1018.                     theList ,
  1019.                     (Array with: bottomDelimiter)]).
  1020.     item _ self initialSelection.
  1021.     selection _ item == nil
  1022.             ifTrue: [0]
  1023.             ifFalse: [itemList findFirst: [:x | x = item]].
  1024.     self positionList.
  1025.     self changeModelSelection: selection! !
  1026.  
  1027.  
  1028. !TextView methodsFor: 'displaying'!
  1029.  
  1030. displayView
  1031.  
  1032.     self isUnlocked
  1033.         ifTrue: [self display]
  1034.         ifFalse: [self clearInside. self controller display]! !
  1035.  
  1036.